package com.cgy.utils.base.reflect;

import static com.cgy.utils.base.constant.Strings.EMPTY;
import static com.cgy.utils.base.reflect.ReflectHelper.VOID;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;

import com.cgy.utils.base.arrays.ArraysHelper;
import com.cgy.utils.base.map.MapHelper;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import lombok.Getter;
import lombok.Setter;

/**
 * 方法处理类
 * @author CHTK
 */
public class ReflectMethodHelper {

    private static ConcurrentHashMap<String, Object> CLASS_METHOD_FIELD_CACHE = new ConcurrentHashMap<>();
    private static final String[] IGNORES = new String[]{"equals", "finalize", "hashCode", "toString", "getClass", "notifyAll", "wait", "notify", "clone"};

    /**
     * 获取类字段
     * @param classes 类
     * @return
     */
    @SuppressWarnings("unchecked")
	public static Map<String, MethodInfoContent> getParameters(Class<?> classes, final String... methods) {
        Map<String, MethodInfoContent> result = MapHelper.newMap();
        if(null != classes) {

            String method = EMPTY;
            if(null != methods && methods.length > 0) {
                method = methods[0];
            }

            String cacheName = classes.getName() + "#" + method;

            if(!CLASS_METHOD_FIELD_CACHE.containsKey(cacheName)) {
                ClassPool classPool = ClassPool.getDefault();
                CtClass ctClass = null;
                try {
                    ctClass = classPool.get(classes.getName());
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }

                CtMethod declaredMethod = null;
                if(StringUtils.isNotBlank(method)) {
                    try {
                        declaredMethod = ctClass.getDeclaredMethod(method);
                    } catch (NotFoundException e) {
                        e.printStackTrace();
                    }
                    MethodInfoContent methodInfoContent = ctMethod(declaredMethod);
                    result.put(methodInfoContent.getMethodName(), methodInfoContent);
                    CLASS_METHOD_FIELD_CACHE.put(cacheName, methodInfoContent);
                } else {
                    CtMethod[] ctMethods = ctClass.getMethods();
                    if(null != ctMethods) {
                        String methodName;
                        for (CtMethod ctMethod : ctMethods) {
                            methodName = ctMethod.getName();
                            if(null != methodName && ArraysHelper.binarySearch(IGNORES, methodName) < 0) {
                                MethodInfoContent methodInfoContent = ctMethod(ctMethod);
                                CLASS_METHOD_FIELD_CACHE.put(cacheName, methodInfoContent);
                                result.put(methodInfoContent.getMethodName(), methodInfoContent);
                            }
                        }
                    }
                }

            } else {
                result = (Map<String, MethodInfoContent>) CLASS_METHOD_FIELD_CACHE.get(cacheName);
            }
        }

        return result;
    }

    /**
     * 执行方法
     * @param classes 类
     * @param name 方法名
     * @return
     */
    public static Object invokeMethod(final Class<?> classes, final String name, final Object... objects) {
        Method method = getMethod(classes, name);
        if(null != method) {
            Class<?> returnType = method.getReturnType();
            if(VOID.equals(returnType.getSimpleName())) {
                return null;
            } else {
                try {
                    return method.invoke(classes.newInstance(), objects);
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }
        return null;
    }


    /**
     * 获取方法
     * @param classes 类
     * @param name 方法名
     * @return
     */
    public static Method getMethod(final Class<?> classes, final String name) {
        if(null != classes && StringUtils.isNotBlank(name)) {
            try {
                return classes.getDeclaredMethod(name);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 设置值
     * @param classes 类
     * @param params 数据
     * @return
     */
    public static <T>T putValue(final Class<T> classes, final Map<String, Object> params) {
        if(null != classes && !MapHelper.isEmpty(params)) {
            T t = null;
            try {
                t = classes.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            Field[] declaredFields = classes.getDeclaredFields();
            if(null != declaredFields) {
                for (Field field : declaredFields) {
                    String name = field.getName();
                    if(params.containsKey(name)) {
                        field.setAccessible(true);
                        try {
                            field.set(t, params.get(name));
                        } catch (IllegalAccessException e) {
                            continue;
                        }
                    }
                }
            }
            return t;
        }
        return null;
    }

    /**
     * 获取方法信息
     * @param ctMethod
     */
    public static MethodInfoContent ctMethod(final CtMethod ctMethod) {
        MethodInfoContent methodInfoContent = new MethodInfoContent();
        //方法名
        String name = ctMethod.getName();
        methodInfoContent.setMethodName(name);
        //返回类型
        try {
            CtClass returnType = ctMethod.getReturnType();
            methodInfoContent.setReturnName(returnType.getName());
            methodInfoContent.setReturnClass(returnType.toClass());
        } catch (Exception e) {
        }

        Map<String, ParameterInfoContent> stringParameterInfoContentMap = ctParameter(ctMethod);
        methodInfoContent.setParameterInfoContents(stringParameterInfoContentMap);

        return methodInfoContent;
    }
    /**
     *
     * 获取方法参数
     * @param ctMethod
     * @return
     */
    public static Map<String, ParameterInfoContent> ctParameter(final CtMethod ctMethod) {

        Map<String, ParameterInfoContent> result = new HashMap<>();

        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        if(null == codeAttribute) {
            throw new NullPointerException(methodInfo.getName());
        }

        LocalVariableAttribute attribute = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        CtClass[] parameterTypes = new CtClass[0];
        try {
            parameterTypes = ctMethod.getParameterTypes();
        } catch (NotFoundException e) {
            e.printStackTrace();
        }

        int line = 0;
        if(null != parameterTypes && (line = parameterTypes.length) > 0) {

            CtClass ctClass = null;
            String parameterName = null;
            ParameterInfoContent parameterInfoContent;
            for (int i = 0; i < line; i++) {
                ctClass = parameterTypes[i];
                parameterName = attribute.variableName(i);

                parameterInfoContent = new ParameterInfoContent();
                parameterInfoContent.setParameterName(parameterName);
                parameterInfoContent.setParameterType(ctClass.getName());
                parameterInfoContent.setParameterClass(ctClass.getClass());
                parameterInfoContent.setLocalVariableAttribute(attribute);

                result.put(parameterName, parameterInfoContent);
            }
        }


        return result;
    }

    @Getter
    @Setter
    public static class ParameterInfoContent {
        private String parameterType;
        private String parameterName;
        protected Class<?> parameterClass;
        private Map<String, ReflectAnnotationHelper.AnnotationInfoContent> annotationInfoContents;

        protected LocalVariableAttribute localVariableAttribute;
    }

    @Getter
    @Setter
    public static class MethodInfoContent {
        private String className;
        private String methodName;
        private String returnName;
        protected Class<?> returnClass;
        private Map<String, ParameterInfoContent> parameterInfoContents;
        private Map<String, ReflectAnnotationHelper.AnnotationInfoContent> annotationInfoContents;
    }
}
